/* Cell Based Lighting */

#include "LightUtils.hlsl"

// Thread Count
#define NUM_THREADS_X	16
#define NUM_THREADS_Y	16
#define NUM_THREADS_Z	1
#define MAX_PASSES		8

// Dispatch Info
cbuffer DispatchBuffer : register(b0)
{
	uint	LightCount;
	float3	CameraPosition;
};

// Compute Input
struct ComputeInput
{
	uint3	GroupID				: SV_GroupID;
	uint	GroupIndex			: SV_GroupIndex;
	uint3	GroupThreadID		: SV_GroupThreadID;
	uint	GroupThreadIndex	: SV_DispatchThreadID;
};

// Textures
Texture2D<float4> g_PositionTexture			: register(t0);
Texture2D<float4> g_NormalTexture			: register(t1);
Texture2D<float4> g_AlbedoTexture			: register(t2);
Texture2D<float4> g_AmbientTexture			: register(t3);
Texture2D<float>  g_DepthTexture			: register(t4);

StructuredBuffer<LightInfo>	g_LightBuffer	: register(t5);

RWTexture2D<float4> g_OutputTexture			: register(u0);

groupshared uint g_VisibleLightCount = 0;
groupshared uint g_VisibleLightIndices[1024];



// Does a Directional Light intersect with this Tile?
bool DoesLightIntersect_Directional(LightInfo light, ComputeInput input)
{
	uint2 basePixelIndex = uint2(0, 0);
	basePixelIndex.x = (NUM_THREADS_X * input.GroupID.x);
	basePixelIndex.y = (NUM_THREADS_Y * input.GroupID.y);

	for (uint py = 0; py < NUM_THREADS_Y; ++py)
	{
		for (uint px = 0; px < NUM_THREADS_X; ++px)
		{
			uint2 pixelIndex = basePixelIndex;
			pixelIndex.x += px;
			pixelIndex.y += py;

			float3 normal = g_NormalTexture[pixelIndex].xyz;
			if (dot(normal, -light.Direction) > 0.0f)
				return true;
		}
	}

	return false;
}
// Does an Omni Light intersect with this Tile?
bool DoesLightIntersect_Omni(LightInfo light, ComputeInput input)
{
	//bool bIntersects = false;

	uint2 basePixelIndex = uint2(0, 0);
	basePixelIndex.x = (NUM_THREADS_X * input.GroupID.x);
	basePixelIndex.y = (NUM_THREADS_Y * input.GroupID.y);

	for (uint py = 0; py < NUM_THREADS_Y; ++py)
	{
		for (uint px = 0; px < NUM_THREADS_X; ++px)
		{
			uint2 pixelIndex = basePixelIndex;
			pixelIndex.x += px;
			pixelIndex.y += py;

			float4 position = g_PositionTexture[pixelIndex];
			if (position.w < 0.1f)
				return false;
			float dist = distance(light.Position, position.xyz);
			if (dist <= light.Range)
				return true;
		}
	}

	return false;
}
// Does a Light intersect with this Tile?
bool DoesLightIntersect(LightInfo light, ComputeInput input)
{
	bool bIntersects = false;

	if (light.LightType == LightType_DirectionalLight)
		bIntersects = DoesLightIntersect_Directional(light, input);
	if (light.LightType == LightType_OmniLight)
		bIntersects = DoesLightIntersect_Omni(light, input);

	return bIntersects;
}

[numthreads(NUM_THREADS_X, NUM_THREADS_Y, NUM_THREADS_Z)]
void main(ComputeInput input)
{
	// Compute to Which pixel we correspond
	uint2 PixelIndex = uint2(0, 0);
	PixelIndex.x = (input.GroupID.x * NUM_THREADS_X) + input.GroupThreadID.x;
	PixelIndex.y = (input.GroupID.y * NUM_THREADS_Y) + input.GroupThreadID.y;

	// Sample the Pixel Info
	float4 Position = g_PositionTexture[PixelIndex];
	float4 Normal = g_NormalTexture[PixelIndex];
	float4 Albedo = g_AlbedoTexture[PixelIndex];
	float4 Ambient = g_AmbientTexture[PixelIndex];
	float Depth = g_DepthTexture[PixelIndex];
	
	// Calculate the Pass Count
	uint ThreadCount = NUM_THREADS_X * NUM_THREADS_Y;
	uint PassCount = (LightCount + ThreadCount - 1) / ThreadCount;
	for (uint passIt = 0; passIt < PassCount; ++passIt)
	{
		uint lightIndex = (passIt * ThreadCount) + input.GroupIndex;
		lightIndex = min(lightIndex, LightCount);

		LightInfo light = g_LightBuffer[lightIndex];

		if (DoesLightIntersect(light, input))
		{
			uint offset;
			InterlockedAdd(g_VisibleLightCount, 1, offset);
			g_VisibleLightIndices[offset] = lightIndex;
		}
	}

	// Sync the Threads
	GroupMemoryBarrierWithGroupSync();

	// Calculate the Light Color for
	// each Light affecting this Pixel
	float3 finalColor = Ambient.xyz;
	for (uint visibleLightIt = 0; visibleLightIt < g_VisibleLightCount; ++visibleLightIt)
	{
		uint lightIndex = g_VisibleLightIndices[visibleLightIt];
		LightInfo light = g_LightBuffer[lightIndex];

		float diffuseTerm = CalculateDiffuseTerm(light, Position, Normal.xyz);
		finalColor += Albedo.xyz * diffuseTerm;

		//float specularTerm = CalculateSpecularTerm(light, Position, Normal.xyz, CameraPosition, Normal.w) * Albedo.w;
		//finalColor += float3(1.0f, 1.0f, 1.0f) * specularTerm;
	}

	// Output the Color
	g_OutputTexture[PixelIndex] = float4(finalColor, 1.0f);

}